/**
 * Copyright (C) 2025 pony working team
 * SPDX-License-Identifier: MulanPSL-2.0
 */

/* includes (local) ----------------------------------------------------------*/
#include "k_defer.h"
#include "k_critical.h"
#include "k_task.h"
/* includes (standard library, system) ---------------------------------------*/
#include <stdlib.h>
#include <limits.h>
/* includes (other library) --------------------------------------------------*/
/* includes (project) --------------------------------------------------------*/
#include <x_common.h>
#include <x_warn.h>
#include <x_bug.h>

#include <a_atomic.h>

/* defines -------------------------------------------------------------------*/
/* typedefs ------------------------------------------------------------------*/
/* macro ---------------------------------------------------------------------*/
/* variables (extern) --------------------------------------------------------*/
/* variables (global) --------------------------------------------------------*/
/* variables (local) ---------------------------------------------------------*/

static task_t __defer[1];
static uint64_t __defer_stack[CONFIG_DEFERRED_STACK_SIZE / sizeof(uint64_t)];
static deferred_job_t *__deferred_head = NULL;
static deferred_job_t *__deferred_tail = NULL;
static deferred_job_t *volatile __deferred_next = (deferred_job_t *)-1;

/* inlines -------------------------------------------------------------------*/
/* functions -----------------------------------------------------------------*/

/**
 * deferred_job_init - initialize a deferred job
 * @job: the job to be initialized
 * @pfn: the callback to be run deffered
 * @arg: the argument to the callback
 *
 * return: 0 on success, negative value on error
 */
int deferred_job_init(deferred_job_t *job, void (*pfn)(uintptr_t), uintptr_t arg)
{
    WARN_ON(job == NULL, errno = ERRNO_DEFERRED_ILLEGAL_JOB; return -1, "Invalid job!");

    WARN_ON(pfn == NULL, errno = ERRNO_DEFERRED_ILLEGAL_PFN; return -1, "Invalid pfn!");

    job->pfn = pfn;
    job->arg = arg;

    atomic_uint_set(&job->busy, 0);

    return 0;
}

static int __deferred_put(uintptr_t arg1, uintptr_t arg2)
{
    deferred_job_t *job = (deferred_job_t *)arg1;

    (void)arg2;

    if ((__defer->status != TASK_STATUS_READY) && (__deferred_next == NULL)) {
        __deferred_next = (void *)job;
        task_ready_q_add(__defer);

        return 0;
    }

    if (__deferred_head == NULL) {
        __deferred_head = job;
    } else {
        __deferred_tail->next = job;
    }

    __deferred_tail = job;

    return 0;
}

/**
 * deferred_job_sched - schedule a deferred job to run
 * @job:  the job to run
 *
 * return: 0 on success, negative value on error
 */
int deferred_job_sched(deferred_job_t *job)
{
    WARN_ON(job == NULL, errno = ERRNO_DEFERRED_ILLEGAL_JOB; return -1, "Invalid job!");

    WARN_ON(job->pfn == NULL, errno = ERRNO_DEFERRED_ILLEGAL_PFN; return -1, "Invalid pfn!");

    /* job already in queue */

    if (unlikely(!atomic_uint_set_eq(&job->busy, 0, 1))) {
        return -1;
    }

    job->next = NULL;

    return do_critical(__deferred_put, (uintptr_t)job, 0);
}

static int __deferred_get(uintptr_t arg1, uintptr_t arg2)
{
    (void)arg1;
    (void)arg2;

    if (__deferred_head == NULL) {
        task_ready_q_del(current);
        current->status = TASK_STATUS_PEND;
    } else {
        __deferred_next = __deferred_head;
        __deferred_head = __deferred_head->next;

        /* if __deferred_head is NULL, it is ok left __deferred_tail as it is */
    }

    return 0;
}

static __noreturn int deferred_task(uintptr_t dummy)
{
    void (*pfn)(uintptr_t);
    uintptr_t arg;
    deferred_job_t *job;

    while (1) {
        __deferred_next = NULL;

        do_critical(__deferred_get, 0, 0);

        job = __deferred_next;

        pfn = job->pfn;
        arg = job->arg;

        atomic_uint_set(&job->busy, 0);

        pfn(arg);
    }
}

/**
 * deferred_lib_init - defer initialization routine
 *
 * return: 0 on success, negative value on error
 */
static int deferred_lib_init(void)
{
    BUG_ON(task_init(__defer, (char *)__defer_stack, CONFIG_DEFERRED_NAME, 0, TASK_OPTION_SYSTEM,
                     CONFIG_DEFERRED_STACK_SIZE, deferred_task, 0) != 0,
           "Fail to initialize deferred task!");

    BUG_ON(task_resume(__defer) != 0, "Fail to resume deffered task!");

    return 0;
}

MODULE_INIT(postkernel, deferred_lib_init);
